package com.asynchfileupload.view;

/**  
* AsynchFileUploadServlet.java - This class(servlet) will handle the file upload and the file upload status requests.  
* @author  Avinash Singh (349933)
* @version 1.0 
* @see AsynchFileUploadConstants,FileUploadListener,FileUploadSession
*/

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

import javax.servlet.http.*;
import org.apache.commons.fileupload.*;

import javax.servlet.ServletException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;

import com.asynchfileupload.data.*;

public class AsynchFileUploadServlet extends HttpServlet implements Servlet,AsynchFileUploadConstants {

    private static final long serialVersionUID = 2740693677625051632L;
    
    /** fileSessionMap stores the file session with a unique key generated based on session ID. */
    private Map<String,FileUploadSession> fileSessionMap = new HashMap<String,FileUploadSession>();
    
    /** dataHandlerMap stores the dataHandler for each upload component instance. */
    private Map<String,String> dataHandlerMap = new HashMap<String, String>();
    
    /** fileSizeMap stores the maximum file size allowed for each upload component instance. */
    private Map<String,String> fileSizeMap = new HashMap<String, String>(); 
    
    private static boolean isDebug = true;

    static FileUploadConfig config = loadConfig();
    
    public AsynchFileUploadServlet() {
        super();
    }

    /**
     * loadConfig() method loads the upload component configuration from the file FileUploadConfig.properties.
     * File should be present in the classpath.It should define minimum of these parameters.
     * e.g. tempStorageLocation=C://temp, maxFileSizeinMemoryKB=1024,dataHandler=com.asynchfileupload.data.DiskStorageDataHandler,debug=true
     */
    
    protected static FileUploadConfig loadConfig()
    {
    	Properties properties = new Properties() ;
    	
    	InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(FILEUPLOADCONFIG_PROPERTIES);
    	
    	try{    		
    		properties.load(is);    		
    		String debugStr = properties.getProperty(ISDEBUG_PARAM);
    		
    		if(debugStr!=null){
    			
    				if(debugStr.equalsIgnoreCase("TRUE"))
    					isDebug = true;
    				else
    					isDebug = false;
    		}
    			
    		System.out.println("AsynchFileUploadServlet:loadConfig() \n" + properties);
    		
    	}catch(FileNotFoundException fe)
    	{
    		if(isDebug) System.out.println("AsynchFileUploadServlet:loadConfig() failed : File Not found in classpath");
    		fe.printStackTrace();
    		
    	}catch(IOException ioe)
    	{
    		if(isDebug) System.out.println("AsynchFileUploadServlet:loadConfig() failed : Failed to read the file");
    		ioe.printStackTrace();
    	}
    	
    	FileUploadConfig config = new FileUploadConfig(properties);
    	
    	return config;
    }
    
    private boolean isNULL(String data)
    {
    	return (data==null || data.equalsIgnoreCase(""))?true:false;
    }
    
    /** doGet() method is used for following task
     *  CREATE_SESSION , CREATE_FILESESSION , DELETE_UPLOADED_FILE ,CANCEL_FILE_UPLOAD and sending the status of upload
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
    	PrintWriter out = response.getWriter();
        HttpSession session = request.getSession();
       
        StringBuffer bufferedResponse = new StringBuffer();
        long bytesRead = 0, contentLength = 0;

        response.setContentType("text/xml");
        
       	if(request.getParameter(CREATE_SESSION_PARAM)!=null ){
       		
        	String groupName = request.getParameter(GROUPNAME_PARAM);        		
        	String dataHandler = request.getParameter(DATAHANDLER_PARAM);
        		
        	if(!isNULL(groupName) && !isNULL(dataHandler))
        			dataHandlerMap.put(groupName, dataHandler);
        		
        	String maxFileSize = request.getParameter(MAXSIZE_PARAM);
        		
        		if(!isNULL(groupName) && !isNULL(maxFileSize))
        			fileSizeMap.put(groupName, maxFileSize);
        		
        		Map<String,String> configParams = new HashMap<String,String>();        		
        		        		
        		bufferedResponse.append(XML_HEADING);
                bufferedResponse.append("<session>\n");
                bufferedResponse.append("<id>" + session.getId() + "</id>\n");
                bufferedResponse.append("</session>");
                out.println(bufferedResponse.toString());
                out.flush();
                out.close();
                return;
                
        	}else if(request.getParameter(CREATE_FILESESSION_PARAM)!=null){
        		
        		String instanceID = request.getParameter(INSTANCEID_PARAM);
        		String groupID = request.getParameter(GROUPNAME_PARAM);
        		
        		FileUploadSession filesession = new FileUploadSession(instanceID,groupID);
        		
        		if(isDebug) System.out.println("AsynchFileUploadServlet.java : Creating File Session - " + filesession.getSessionID());
        		
        		fileSessionMap.put(instanceID + "_" + session.getId(), filesession);
        		
        		bufferedResponse.append(XML_HEADING);
                bufferedResponse.append("<filesession>\n");
                bufferedResponse.append("<instanceID>" + filesession.getInstanceID() + "</instanceID>\n");
                bufferedResponse.append("<groupid>" + filesession.getGroupID() + "</groupid>\n");
                bufferedResponse.append("<fileSessionID>" + filesession.getSessionID() + "</fileSessionID>\n");
                bufferedResponse.append("</filesession>");
                out.println(bufferedResponse.toString());
                out.flush();
                out.close();
                return;
                
        	}else if(request.getParameter(DELETE_FILE_PARAM)!=null){
        		
        		String fileName = request.getParameter(FILENAME_PARAM);
        		String groupID = request.getParameter(GROUPNAME_PARAM);
        		String instanceID = request.getParameter(INSTANCEID_PARAM);
        		String fileSessionID = request.getParameter(FILESESSION_PARAM);
        		
        		String dataHandlerClassName = dataHandlerMap.get(groupID);
                  
                  if(dataHandlerClassName==null)
                 	 dataHandlerClassName = config.getConfig().getProperty(DATAHANDLER_PARAM);
                
                boolean result = deleteFile(dataHandlerClassName, fileSessionID , fileName , request.getParameterMap());
                
                if(result){
                    	if(isDebug) 
                    		System.out.println("AsynchFileUploadServlet.java : File Deleted successfully " + fileName);
                  }else{
                	  
                	  if(isDebug) System.out.println("AsynchFileUploadServlet.java : Exception in deleting the File ");
                	  if(isDebug) System.out.println("AsynchFileUploadServlet.java : inputParams " + request.getParameterMap());
                	  if(isDebug) System.out.println("AsynchFileUploadServlet.java : File " + fileName);
                  }
                
        		bufferedResponse.append(XML_HEADING);
                bufferedResponse.append("<fileDelete>\n");
                bufferedResponse.append("<fileName>" + fileName + "</fileName>\n");
                bufferedResponse.append("<instanceID>" + instanceID + "</instanceID>\n");
                bufferedResponse.append("<groupID>" + groupID + "</groupID>\n");
                bufferedResponse.append("<delete>" + result + "</delete>\n");
                bufferedResponse.append("</fileDelete>");
                out.println(bufferedResponse.toString());
                out.flush();
                out.close();
                return;
                
        	}else if(request.getParameter(CANCEL_FILE_PARAM)!=null){
        		
        		String instanceID = request.getParameter(INSTANCEID_PARAM);
        		fileSessionMap.remove(instanceID + "_" + session.getId());
        		
        		if(isDebug) System.out.println("AsynchFileUploadServlet.java : Canceling File Session - " + instanceID);
        		
        		bufferedResponse.append(XML_HEADING);
                bufferedResponse.append("<fileCancel>\n");
                bufferedResponse.append("<instanceID>" + instanceID + "</instanceID>\n");
                bufferedResponse.append("<cancel>true</cancel>\n");
                bufferedResponse.append("</fileCancel>");
             
                out.println(bufferedResponse.toString());
                out.flush();
                out.close();
             	return;
             	
        	}else{
	    		 	String instanceID = request.getParameter(INSTANCEID_PARAM);
	    	        FileUploadListener listener = null;
	    	        FileUploadSession filesession = fileSessionMap.get(instanceID + "_" + session.getId());
	    	        
	    	        if(filesession ==null){
	    	        	
	    	        	bufferedResponse.append(XML_HEADING);
	                    bufferedResponse.append("<error>\n");
	                    bufferedResponse.append("<instanceID>" + instanceID + "</instanceID>\n");
	                    bufferedResponse.append("<fileSessionID>NULL</fileSessionID>\n");
	                    bufferedResponse.append("</error>");
	                    out.println(bufferedResponse.toString());
	                    out.flush();
	                    out.close();
	                    return;    
	    	        }
	    	        
	    	     	String fileSessionID = filesession.getSessionID();
	             	
	                listener = (FileUploadListener) session.getAttribute(LISTENERID_PREFIX + fileSessionID);
	                
	                if (listener == null){
	                	
	                	bufferedResponse.append(XML_HEADING);
	                    bufferedResponse.append("<error>\n");
	                    bufferedResponse.append("<instanceID>" + instanceID + "</instanceID>\n");
	                    bufferedResponse.append("<fileSessionID>" + fileSessionID + "</fileSessionID>\n");
	                    bufferedResponse.append("<fileName>" + filesession.getFileName() + "</fileName>\n");
	                    bufferedResponse.append("</error>");
	                    out.println(bufferedResponse.toString());
	                    out.flush();
	                    out.close();
	                    return;
	                    
	                }else{
	                    
	                	bytesRead = listener.getBytesRead();
	                    contentLength = listener.getContentLength();
	                    
	                    bufferedResponse.append(XML_HEADING);
	                    bufferedResponse.append("<response>\n");
	                    bufferedResponse.append("<instanceID>" + instanceID + "</instanceID>\n");
	                    bufferedResponse.append("<fileName>" + filesession.getFileName() + "</fileName>\n");
	                    bufferedResponse.append("<groupID>" + filesession.getGroupID() + "</groupID>\n");
	                    bufferedResponse.append("<fileSessionID>" + fileSessionID + "</fileSessionID>\n");
	                    bufferedResponse.append("\t<bytes_read>" + bytesRead + "</bytes_read>\n");
	                    bufferedResponse.append("\t<content_length>" + contentLength + "</content_length>\n");
	                    
	                    String message = filesession.getErrorMessage();
	                	
	                    if (bytesRead == contentLength && filesession.getFileName()!=null && filesession.getStatusIndicator()!=-1){
	                    	
	                    	if(!filesession.isFileUploadStatus()){
	                    		
	                    		if(message==null)
	                    			message = "Could not upload file on server";
	                    		
	                    		bufferedResponse.append("\t<result>FAILED</result>\n");
	                    		
	                    	}else{
	                    		
	                    		bufferedResponse.append("\t<message>" + message + "</message>\n");
	                    		bufferedResponse.append("\t<result>SUCCESS</result>\n");
	                    	}
	                    	
	                    	bufferedResponse.append("\t<finished />\n");
	                        
	                        if(isDebug) System.out.println("AsynchFileUploadServlet.java : Clearing File Session - " + instanceID);
	                        
	                        session.setAttribute(LISTENERID_PREFIX + fileSessionID, null);
	                        fileSessionMap.remove(instanceID + "_" + session.getId());
	                    
	                    }else{
	                    	
	                        long percentComplete = (contentLength!=0)?((100 * bytesRead) / contentLength):0;
	                        bufferedResponse.append("\t<percent_complete>" + percentComplete + "</percent_complete>\n");
	                    }
	                   
	                    bufferedResponse.append("\t<message>" + message + "</message>\n");
	                    bufferedResponse.append("</response>\n");
	                    out.println(bufferedResponse.toString());
	                    out.flush();
	                    out.close();
                    }
        	}        	
    }

    /** doPost() method is used for saving the file.
     * The method uses the apache commons upload for reading the file data.It also stores the input parameter in a Map.
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	
    	HttpSession session = request.getSession();         
    	 
    	Map<String,String> inputParams = new HashMap<String,String>(); 
    	InputStream uploadedStream = null;
    	String fileName = "";
    	
    	String tempStorageLocation = config.getConfig().getProperty(FILESTORAGE_PARAM);
    	String maxCacheSize = config.getConfig().getProperty(FILESIZE_PARAM);
    	
    	DiskFileItemFactory factory = new DiskFileItemFactory();
        if(tempStorageLocation !=null) 	factory.setRepository(new File(tempStorageLocation));
        if(maxCacheSize !=null)  factory.setSizeThreshold(Integer.parseInt(maxCacheSize)*1024);
   
        FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(getServletContext());
        factory.setFileCleaningTracker(fileCleaningTracker);
   
        ServletFileUpload upload = new ServletFileUpload(factory);
         
        String requestURL = request.getRequestURL().toString();
        String instanceID = requestURL.substring(requestURL.lastIndexOf('/') + 1);
        
        String groupName = instanceID.split("_")[0];
       
        if(isDebug) System.out.println("AsynchFileUploadServlet.java : File Request  " + instanceID);
        
        FileUploadSession filesession = fileSessionMap.get(instanceID + "_" + session.getId());
     	String fileSessionID = filesession.getSessionID();
     	
     	if(isDebug) System.out.println("AsynchFileUploadServlet.java : File Session  " + fileSessionID);
        
        FileUploadListener listener = new FileUploadListener(request,fileSessionID);
        
        session.setAttribute(LISTENERID_PREFIX + fileSessionID,listener);
        
        List uploadedItems = null;FileItem fileItem = null;
        
        String maxSizeStr = fileSizeMap.get(groupName);
        
        long maxFileSize = (maxSizeStr!=null)? Integer.parseInt(maxSizeStr):-1;
        
        try {
        	
        	upload.setProgressListener(listener);
            uploadedItems = upload.parseRequest(request);
            
            Iterator i = uploadedItems.iterator();

            while (i.hasNext()) {
              
            	fileItem = (FileItem) i.next();
                
                if (fileItem.isFormField() == false) {
                	
                	if (fileItem.getSize() > 0) {
                		
                		File uploadedFile = null;
                        String fullFileName = fileItem.getName(),slashType = (fullFileName.lastIndexOf("\\") > 0) ? "\\" : "/";
                        
                        int startIndex = fullFileName.lastIndexOf(slashType);
                        fileName = fullFileName.substring(startIndex + 1, fullFileName.length());
                        
                        filesession.setFileName(fileName);
                        
                		if(maxFileSize ==-1 || fileItem.getSize()<(maxFileSize*1024*1024)){
                			
	                	    if(isDebug) System.out.println("AsynchFileUploadServlet.java : Saving file " + fileName);
	                        uploadedStream = fileItem.getInputStream();
	                        
                		}else{
                			
                		    if(isDebug) System.out.println("AsynchFileUploadServlet.java : File Size exceeded max limit " + maxFileSize*1024*1024 + "\nFile " + fileName + ",size=" + fileItem.getSize());
                		    
                		    filesession.setFileName(fileName);
 	                        filesession.setStatusIndicator(1);
 	                        filesession.setFileUploadStatus(false);
 	                        filesession.setErrorMessage("File Size exceeded max limit of " + maxFileSize + "MB");
 	                        
 	                        fileSessionMap.put(instanceID + "_" + session.getId(), filesession);
 	                        throw new Exception("File Size exceeded max limit of " + maxFileSize + "MB");
 	                   }
                    }
                }else{
                	
                	String paramName = fileItem.getFieldName();
                	String paramValue = fileItem.getString();
                	inputParams.put(paramName, paramValue);
                }
            }
           
            String dataHandlerClassName = dataHandlerMap.get(groupName);
             
            if(dataHandlerClassName==null)
            	dataHandlerClassName = config.getConfig().getProperty(DATAHANDLER_PARAM);
             
                 
            boolean result = saveFile(dataHandlerClassName,fileSessionID,uploadedStream,fileName,inputParams);
         
            filesession.setStatusIndicator(1);
            filesession.setFileUploadStatus(result);
         
            fileSessionMap.put(instanceID + "_" + session.getId(), filesession);
         
              if(result)
                if(isDebug) System.out.println("AsynchFileUploadServlet.java : File Saved successfully ");
              else{
            	  
            	if(isDebug) System.out.println("AsynchFileUploadServlet.java : Exception is saving the File ");
            	if(isDebug) System.out.println("AsynchFileUploadServlet.java : inputParams " + inputParams);
            	if(isDebug) System.out.println("AsynchFileUploadServlet.java : File " + fileName);
              }
                            
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** saveFile() method is used for saving the file by making DataHandler call using reflection. */
    private boolean saveFile(String dataHandlerClassName,String fileSessionID,InputStream uploadedStream,String fileName,Map<String,String> inputParams)
	{
		boolean result= false;

		try{
	         Class dataHandlerClass = Class.forName(dataHandlerClassName);
	        
	         Constructor defaultConst = dataHandlerClass.getConstructor();
	         IDataHandler dataHandlerClassObj = (IDataHandler)dataHandlerClass.newInstance();
			
	         result = dataHandlerClassObj.storeFile(uploadedStream,fileSessionID,fileName,inputParams);
	         
		}catch(ClassNotFoundException cnfe)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Data Handler class not found " + dataHandlerClassName);
			cnfe.printStackTrace();
		}
		catch(NoSuchMethodException nme)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Method storeFile(InputStream is,Map<String,String> inputParams) not foud in Data Handler class " + dataHandlerClassName);
			nme.printStackTrace();
			
		}catch(Exception e)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Could not instantiate Data Handler class " + dataHandlerClassName);
			e.printStackTrace();
		}
		
         return result;
	}
	
    /** deleteFile() method is used for deleting the file by making DataHandler call using reflection. */
	private boolean deleteFile(String dataHandlerClassName,String fileSessionID,String fileName,Map<String,String> inputParams)
	{
		boolean result= false;

		try{
	         Class dataHandlerClass = Class.forName(dataHandlerClassName);
	        
	         Constructor defaultConst = dataHandlerClass.getConstructor();
	         IDataHandler dataHandlerClassObj = (IDataHandler)dataHandlerClass.newInstance();
			
	         result = dataHandlerClassObj.deleteFile(fileSessionID,fileName, inputParams);
	         
		}catch(ClassNotFoundException cnfe)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Data Handler class not found " + dataHandlerClassName);
			cnfe.printStackTrace();
		}
		catch(NoSuchMethodException nme)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Method deleteFile(InputStream is,Map<String,String> inputParams) not foud in Data Handler class " + dataHandlerClassName);
			nme.printStackTrace();
			
		}catch(Exception e)
		{
			if(isDebug) System.out.println("AsynchFileUploadServlet.java : Could not instantiate Data Handler class " + dataHandlerClassName);
			e.printStackTrace();
		}	
        return result;
	}

	public static boolean isDebug() {
		return isDebug;
	}

	public static void setDebug(boolean isDebug) {
		AsynchFileUploadServlet.isDebug = isDebug;
	}

	public static FileUploadConfig getConfig() {
		return config;
	}

	public static void setConfig(FileUploadConfig config) {
		AsynchFileUploadServlet.config = config;
	}

}